import requests
from lxml import etree
import pandas as pd
import time
from datetime import datetime
import random
import re
from tqdm import tqdm
import os
from urllib.parse import urlparse


file_path=r'E:\pycharm\pythonProject\4_Project\3_58二手房\1_舟山 2025-06-18'

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0',
    'referer': 'https://zhoushan.58.com/ershoufang/',
}

# 必填!!！从浏览器获取的Cookies,否则可能被反爬!!!
# 需要转换为：字典格式，在线转换工具，Eg  https://www.kgtools.cn/compression/cookie
cookies = {
    "f": "n",
    "commontopbar_new_city_info": "144%7C%E8%88%9F%E5%B1%B1%7Czhou",
    "commontopbar_ipcity": "qd%7C%E9%9D%92%E5%B2%9B%7C0",
    "SECKEY_ABVK": "nqPtROz/g4vFSkuTILmWfZs50KdFKEHAGISlo9ROOR0%3D",
    "BMAP_SECKEY": "nqPtROz_g4vFSkuTILmWfRTIECk4wov7sXIO6uwtPDQZTNBeeLJ65Zu9snuQfO5jbi-V7_IuQ1VI8iqP3eiZHBlF6dWc7B7uhNiv01vt3lMJgYe6R9yNBQKt3ZNz-t7qTr93wXkWdS6tKp7LdwDOKXZFJDU-2LlmXtCFUwyKH5-mBWbpPdjOLyf-PvA0fiz6",
    "id58": "CrINbWcM2c8yv+sgFG0GAg==",
    "xxzlclientid": "bc9b3943-0c13-42a3-bb1e-1728895440470",
    "xxzlxxid": "pfmxeoAHLsU3Lqf/f4jWwzGVVo/X11KXFRWPiphrYocxlX/VXg7Reg3BQIZCJ8tPN6cd",
    "city": "bj",
    "Hm_lvt_3f405f7f26b8855bc0fd96b1ae92db7e": "1749172980",
    "Hm_lpvt_3f405f7f26b8855bc0fd96b1ae92db7e": "1749172980",
    "HMACCOUNT": "1222F827FD22A500",
    "ipcity": "jn%7C%u6D4E%u5357",
    "myfeet_tooltip": "end",
    "58tj_uuid": "003b0505-3c45-426a-9dea-1e5cddc68fd8",
    "new_uv": "1",
    "utm_source": "",
    "spm": "",
    "init_refer": "https%253A%252F%252Fwww.58.com%252Fershoufang%252F",
    "als": "0",
    "aQQ_ajkguid": "1626C16B-C821-4D08-A884-C1C67B11E35E",
    "sessid": "B665D4A6-2EDE-49CD-9B80-9F62B14D1136",
    "ajk-appVersion": "",
    "fzq_h": "cc10bc7b4264326c0e778d5e79173365_1749173009689_0d511644d67d4f4db0d13a3d6fb686ed_1903717483",
    "new_session": "0",
    "58_ctid": "8481",
    "is_58_pc": "1",
    "ctid": "8481",
    "xxzlbbid": "pfmbM3wxMDM0NnwxLjEwLjB8MTc0OTE3MzcwNTE4MDIyMzE4MXx5MjlCWWdtSVhEZEpjT1dhMmR3QUhUQTdVSW9vNHJBS0xUSWJja3VKUzJrPXxjMWZlMDIyMWU1ZmFmMzljYjYzYzM2NTg3NTA2ZmIzZl8xNzQ5MTczNzA0NjY5XzAxMzkyZGMzMDgyNjRlYWE4ODgwNmQyNjE2YTlkMDA1XzE5MDM3MTc0ODN8YzA0ZTU4NGUxNjk0MDNjZjY2NDJlNWZjOTcxMjJlYTlfMTc0OTE3MzcwNDcyN18yNTQ="
}

base_url = 'https://zhoushan.58.com/ershoufang/'
params={
'PGTID':'0d30000c-0212-1d0c-6fe1-5deecaca7124',
'ClickID':'1'
}
all_houses = []


# 逐页、获取单个[1级？？网页]中的[房源列表]数据
# def get_Pagelink_HouseData(page):
    # 第一页无pg1。只要page>1，网页加pgx，否则不加：base_url
for page in range(51):
    url = f'{base_url}p{page+1}/' # if page > 1 else base_url
    response=requests.get(url,params=params,headers=headers,cookies=cookies,timeout=5)
    response.encoding='utf-8'
    print(f'✅ 正在采集[第{page+1}页]：{response.url}')

    html=etree.HTML(response.text)
    # print(html)
    time.sleep(random.uniform(0.32,0.7))

    HouseLists_Pagelink=html.xpath('//*[@id="esfMain"]/section/section[3]/section[1]/section[2]/div')
    # print(HouseLists_FirstUrl)
    if not HouseLists_Pagelink:
        print(f'⚠️ 未找到房源列表，可能XPath错误或页面结构发生变化')
    else:
        print(f'🔍 [第{page+1}页]：成功找到{len(HouseLists_Pagelink)}条房源')


    HouseDatas_Pagelink= {}
    for idx,list in enumerate(HouseLists_Pagelink):    #[:1]
        # 1、房源标题
        title_pagelink= list.xpath('.//a/div[2]/div[1]/div[1]/h3/text()')[0]
        HouseDatas_Pagelink['房源标题_页码列表页']=title_pagelink.strip()  #数据清晰：去除收尾空格
        # print(type(title))  #<class 'list'>

        # 2、房源特色标签
        tags_raw= list.xpath('.//a/div[2]/div[1]/section/div[3]/span/text()')
        if tags_raw:
            tags=' | '.join(tags_raw)
        else:
            tags=''
        HouseDatas_Pagelink['房源标签_页码列表页'] = tags

        # 3、户型
        layout_raw=list.xpath('.//a/div[2]/div[1]/section/div[1]/p[1]//text()')
        layout=''.join(layout_raw).replace(' ','').strip()
        HouseDatas_Pagelink['户型_页码列表页'] = layout

        # 4、建筑面积
        area_raw=list.xpath('.//a/div[2]/div[1]/section/div[1]/p[2]/text()')  #<class 'list'>
        #area_raw[0]的作用是：访问列表中的第1个元素。因为list.xpath() 返回的是1个列表（即使只匹配到1个结果）'list'>
        # area_raw[0] 的类型是 字符串（str），相当于完成了字符转换
        area=area_raw[0].replace('\n','').replace(' ','').replace('㎡','').strip()
        HouseDatas_Pagelink['建筑面积（㎡）_页码列表页'] = int(area)

        # 5、房屋朝向
        face=list.xpath('.//a/div[2]/div[1]/section/div[1]/p[3]/text()')[0]
        HouseDatas_Pagelink['房屋朝向_页码列表页'] = face

        # 6、所在楼层
        floor_raw=list.xpath('.//a/div[2]/div[1]/section/div[1]/p[4]/text()')[0]
        floor=floor_raw.replace('\n','').replace(' ','')
        HouseDatas_Pagelink['所在楼层_页码列表页'] = floor

        # 7、建设年代
        buildtime_raw = list.xpath('.//a/div[2]/div[1]/section/div[1]/p[5]/text()')   #.//a/div[2]/div[1]/section/div[5]/p[2]/text()
        if buildtime_raw:   #检查是否为空值：如果不为空
           buildtime=buildtime_raw[0].strip()   #提取并清理文本
        else:
            buildtime='未知'  #如果为空，则赋新值：未知
        HouseDatas_Pagelink['建设年代_页码列表页'] = buildtime

        # 8、小区名称
        name = list.xpath('.//a/div[2]/div[1]/section/div[2]/p[1]/text()')[0]
        HouseDatas_Pagelink['小区名称_页码列表页'] = name

        # 9、小区/房源地址
        address_raw = list.xpath('.//a/div[2]/div[1]/section/div[2]/p[2]//text()')
        address='-'.join(address_raw)
        HouseDatas_Pagelink['小区/房源地址_页码列表页'] = address

        # 10、2级详情链接
        detail_link = list.xpath('.//a/@href')[0]
        HouseDatas_Pagelink['2级房源详情链接'] = detail_link

        # print(HouseDatas_Pagelink)




# 逐一访问、并提取：[2级？？网页]中的[房源]数据
# def get_DetailLink_HouseData(url):
        response=requests.get(detail_link,headers=headers,cookies=cookies,timeout=5)
        response.encoding='utf-8'
        print(f' 🧐 正在采集[第{page+1}页]-的-[第{idx+1}条]房源详情页：{detail_link}')

        html=etree.HTML(response.text)
        # print(html)
        time.sleep(random.uniform(0.5,1.1))


        HouseDatas_DetailLink= {}
        # 1、房源标题
        title = html.xpath('//*[@id="__layout"]/div/div[3]/div[1]/div[3]/h1/text()')[0].strip()
        HouseDatas_DetailLink['房源标题_详情页'] = title

        # 2、房屋编码
        number = html.xpath('//*[@id="__layout"]/div/div[3]/div[1]/div[3]/div[2]/text()')[0].replace('房屋编码：','')
        HouseDatas_DetailLink['房源编码'] = int(number)

        # 3、房源照片与视频
        # 3.1 采集[视频与图片]网址urls
        video_urls = html.xpath('//*[@id="jp_video_0"]/@src')
        HouseDatas_DetailLink['VR视频_链接'] = video_urls[0] if video_urls else '无'

        img_urls_raw = html.xpath('//img[contains(@class, "gallery-indicator-image-item")]/@src')   #更简单的 XPath
        img_urls=img_urls_raw if img_urls_raw else '无'  #保持列表格式，并后面可直接循环列表网址，下载文件
        HouseDatas_DetailLink['房源相册_链接'] = ','.join(img_urls_raw)  #存储在excel中时，转为字符串，并用逗号分隔
        # print(f'  👉 查询到{len(video_urls)}条视频、{len(img_urls)}张照片链接')

        # 3.2创建存储目录，并下载视频与图片
        # 3.2.1、以[房源标题]为名称，创建文件夹
        replace_chars=r'[\\/:*?"<>|\.,;=+&%$#@!~`\']'
        folder_name=re.sub(replace_chars,' ',title)

        # 递归创建文件夹 方法，自动跳过已存在的文件夹
        os.makedirs(f'{file_path}\{folder_name}',exist_ok=True)

        # 3.2.2、从URL中提取文件名称，并下载保存视频/图片
        # 同时遍历:图片链接列表（image_urls）和视频链接列表（video_urls），并为每个链接分配一个序号 i
        for i,url in enumerate((video_urls+img_urls)):   #[:2]

            #（1） 解析[照片与视频网址]，并从中提取[文件名称]
            parsed_url=urlparse(url)
            # print(parsed_url)   #包括6个参数：scheme='https', netloc='pic1.ajkimg.com', path='/display/58ajk/bc2393bf026d6f7ab249254d35e77913/640x420c.jpg', params='', query='frame=1', fragment=''。
            # print(type(parsed_url))  #<class 'urllib.parse.ParseResult'>
            path_parts= parsed_url.path.split('/')  # 拆分parsed_url中的参数path
            # print(path_parts)
            file_name=f'{i+1}_{path_parts[-2]}_{path_parts[-1]}'
            # print(file_name)

            #（2）下载图片/视频，并保存。。。可以先注释掉不下载
            try:
                response=requests.get(url,stream=True)    #分块（流式）下载大文件（如图片、视频等），而不是一次性加载整个文件到内存
                if response.status_code==200:
                    with open(f'{file_path}\{folder_name}\{file_name}','wb') as f:
                        for chunk in response.iter_content(1024):    #逐块（chunk），如：按指定块大小（如 1KB）迭代数据、读取文件
                            f.write(chunk)
                    # print(f'   ✔️ 图片/视频已保存至：{folder_name}\{file_name}')
                else:
                    print(f'   ❌下载失败，状态码：{response.status_code}')
            except Exception as e:
                print(f'     ❌发生错误,图片/视频未能下载：e')

        # 4、挂牌总价
        price_total = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[2]/div[1]/div[1]/span[1]/text()')[0].strip()
        HouseDatas_DetailLink['挂牌总价'] = float(price_total)

        # 5、小区均价
        price_average = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[2]/div[1]/div[2]/div[1]/text()')[0].strip().replace('元/㎡', '')
        # layout = ''.join(layout_raw).replace(' ', '').strip()
        HouseDatas_DetailLink['小区均价(元/㎡)'] = float(price_average)

        # 6、户型
        layout_raw = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[3]/div[1]/div[1]//text()')
        layout = ''.join(layout_raw).replace(' ', '').strip()
        HouseDatas_DetailLink['户型'] = layout

        # 7、建筑面积
        area = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[3]/div[2]/div[1]/i/text()') [0].strip()
        HouseDatas_DetailLink['建筑面积（㎡）'] = area

        # 8、房屋朝向
        face = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[3]/div[3]/div[1]/i/text()')[0].strip()
        HouseDatas_DetailLink['房屋朝向'] = face

        # 9、所在楼层
        floor_raw = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[3]/div[1]/div[2]/text()')[0].strip()
        # 高层(共24层)、共5层。避免第2种信息不全的情况
        if'(' in floor_raw and ')' in floor_raw:
            floor=floor_raw.split('(')
            HouseDatas_DetailLink['所在楼层'] = floor[0]
            HouseDatas_DetailLink['全部楼层'] = floor[1].replace(')','')
        else:
            HouseDatas_DetailLink['所在楼层'] = '未知'
            HouseDatas_DetailLink['全部楼层'] = floor_raw

        # 10、装修情况
        decoration = html.xpath('.//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[3]/div[2]/div[2]/text()')[0].strip()
        HouseDatas_DetailLink['装修情况'] = decoration

        # 11、建设年代
        buildtime_raw = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[3]/div[3]/div[2]/text()')[0].strip()
        if buildtime_raw:  # 检查是否为空值：如果不为空
            buildtime = buildtime_raw.split('/')[0]
        else:
            buildtime = '未知'  # 如果为空，则赋新值：未知
        HouseDatas_DetailLink['建设年代'] = buildtime

        # 12、房源特色
        tags = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[4]/div[1]/span/text()')
        HouseDatas_DetailLink['房源特色_详情页'] = tags[0].strip() if tags else''

        # 13、小区名称
        name = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[4]/div[2]/div[1]/a[1]/text()')[0].strip()
        HouseDatas_DetailLink['小区名称'] = name

        # 14、所属区域
        block_raw = html.xpath('//*[@id="__layout"]/div/div[3]/div[2]/div[2]/div[1]/div[4]/div[2]/div[2]/span[2]//text()')
        block = ''.join(block_raw).replace(' ', '').strip().replace('\xa0','')
        HouseDatas_DetailLink['所属区域'] = block

        # 15、产权性质
        house_property = html.xpath('//*[@id="houseInfo"]/table/tbody/tr[1]/td[2]/span[2]/text()')[0].strip()
        HouseDatas_DetailLink['产权性质'] = house_property

        # 16、产权年限
        years_limit = html.xpath('//*[@id="houseInfo"]/table/tbody/tr[2]/td[2]/span[2]/text()')[0].strip()
        HouseDatas_DetailLink['产权年限'] = years_limit

        # 17、物业类型
        house_type = html.xpath('//*[@id="houseInfo"]/table/tbody/tr[1]/td[3]/span[2]/text()')[0].strip()
        HouseDatas_DetailLink['物业类型'] = house_type

        # 18、参考预算
        budget = html.xpath('//*[@id="houseInfo"]/table/tbody/tr[3]/td[2]/span[2]/text()')[0].strip()
        HouseDatas_DetailLink['参考预算'] = budget

        # 19、发布时间
        post_time = html.xpath('//tr[contains(., "发布时间")]/td[2]/span[2]/text()')[0].strip()  #.//*[@id="houseInfo"]/table/tbody/tr[6]/td[2]/span[2]/text()
        HouseDatas_DetailLink['发布时间'] = post_time

        # 20、更新日期
        update_time = html.xpath('//tr[contains(., "更新日期")]/td[2]/span[2]/text()')[0].strip()   #//*[@id="houseInfo"]/table/tbody/tr[7]/td[2]/span[2]/text()
        HouseDatas_DetailLink['更新日期'] = update_time



        house_data={**HouseDatas_Pagelink,**HouseDatas_DetailLink}

        # 自定义排序：房源信息列
        column_order=['房源编码',
                      '房源标题_页码列表页',
                      '房源标题_详情页',
                      '房源标签_页码列表页',
                      '房源特色_详情页',
                      '挂牌总价',
                      '户型_页码列表页',
                      '户型',
                      '建筑面积（㎡）_页码列表页',
                      '建筑面积（㎡）',
                      '房屋朝向_页码列表页',
                      '房屋朝向',
                      '所在楼层_页码列表页',
                      '所在楼层',
                      '全部楼层',
                      '装修情况',
                      '建设年代_页码列表页',
                      '建设年代',
                      '小区名称_页码列表页',
                      '小区名称',
                      '所属区域',
                      '小区/房源地址_页码列表页',
                      '小区均价(元/㎡)',
                      '产权性质',
                      '产权年限',
                      '物业类型',
                      '参考预算',
                      '发布时间',
                      '更新日期',
                      '2级房源详情链接',
                      'VR视频_链接',
                      '房源相册_链接'
                      ]

        # 按colum_order指定顺序重新排列
        house_orded={col:house_data[col] for col in column_order if col in house_data}

        all_houses.append(house_orded)

    # print(HouseDatas_DetailLink)


# 保存到Excel
if all_houses:
    df=pd.DataFrame(all_houses)
    total_houses=len(df)

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    excel_file=f'舟山二手房_{timestamp}.xlsx'
    save_path=os.path.join(file_path,excel_file)
    df.to_excel(save_path,index=False)

    print(f"🏡 成功采集到：{total_houses}条房源数据，已保存至：{save_path}")


